package com.uuah.server.config.spring.parsers.delegate;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

import com.uuah.server.config.spring.UuahHierarchicalBeanDefinitionParserDelegate;
import com.uuah.server.config.spring.parsers.AbstractUuahBeanDefinitionParser;
import com.uuah.server.config.spring.parsers.IUuahDefinitionParser;
import com.uuah.server.config.spring.parsers.IUuahDefinitionParserConfiguration;
import com.uuah.server.config.spring.parsers.PreProcessor;
import com.uuah.server.config.spring.parsers.assembly.configuration.IPropertyConfiguration;
import com.uuah.utils.StringUtils;

/**
 * This allows a set of definition parsers to be used, one after another, to
 * process the same element. This lets multiple beans be generated from a single
 * element.
 *
 * <p>
 * Since each bean typically needs a spearate name, this class guarantees that
 * the name and id attributes are reset before each call. Delegates can then
 * modify these on the element without worrying about interfering with other
 * parsers.
 * </p>
 *
 * <p>
 * Typically, subclasses will add additional processing with
 * {@link com.uuah.server.config.spring.parsers.PreProcessor} and
 * {@link com.uuah.server.config.spring.parsers.PostProcessor} anonymous classes.
 * </p>
 */
public abstract class AbstractSerialDelegatingDefinitionParser extends
		AbstractDelegatingDefinitionParser {

	private int index = 0;
	private boolean first;
	private boolean doReset;
	private String originalId;
	private String originalName;
	private Set handledExceptions = new HashSet();

	public AbstractSerialDelegatingDefinitionParser() {
		this(true); // by default, reset name
	}

	/**
	 * @param doReset
	 *            Should the name be reset after called. This is typically true
	 *            (it protects the parent from changes made by children) unless
	 *            this is itself nested.
	 */
	public AbstractSerialDelegatingDefinitionParser(boolean doReset) {
		this.doReset = doReset;
	}

	public AbstractBeanDefinition uuahParse(Element element,
			ParserContext parserContext) {
		if (index == 0 || index >= size()) {
			first = true;
			index = 0;
		} else {
			first = false;
		}
		AbstractBeanDefinition bean = null;
		while (null == bean && index < size()) {
			try {
				IUuahDefinitionParser parser = getDelegate(index);
				bean = doSingleBean(index++, parser, element, parserContext);
			} catch (RuntimeException e) {
				if (isExceptionHandled(e)) {
					bean = null;
				} else {
					throw e;
				}
			}
		}
		if (null != bean) {
			if (index == size()) {
				bean
						.removeAttribute(UuahHierarchicalBeanDefinitionParserDelegate.UUAH_REPEAT_PARSE);
			} else {
				bean
						.setAttribute(
								UuahHierarchicalBeanDefinitionParserDelegate.UUAH_REPEAT_PARSE,
								Boolean.TRUE);
			}
		}
		return bean;
	}

	protected boolean isExceptionHandled(Exception e) {
		return handledExceptions.contains(e.getClass());
	}

	protected AbstractBeanDefinition doSingleBean(int index,
			IUuahDefinitionParser parser, Element element,
			ParserContext parserContext) {
		return parser.uuahParse(element, parserContext);
	}

	protected IUuahDefinitionParserConfiguration addDelegate(
			IUuahDefinitionParser delegate) {
		delegate.registerPreProcessor(new PreProcessor() {
			public void preProcess(IPropertyConfiguration config, Element element) {
				if (first) {
					originalId = element
							.getAttribute(AbstractUuahBeanDefinitionParser.ATTRIBUTE_ID);
					originalName = element
							.getAttribute(AbstractUuahBeanDefinitionParser.ATTRIBUTE_NAME);
				} else if (doReset) {
					resetNameAndId(element);
				}
			}
		});
		return super.addDelegate(delegate);
	}

	protected void resetNameAndId(Element element) {
		resetAttribute(element, AbstractUuahBeanDefinitionParser.ATTRIBUTE_ID,
				originalId);
		resetAttribute(element, AbstractUuahBeanDefinitionParser.ATTRIBUTE_NAME,
				originalName);
	}

	protected void resetAttribute(Element element, String name, String value) {
		if (StringUtils.isEmpty(value)) {
			if (element.hasAttribute(name)) {
				element.removeAttribute(name);
			}
		} else {
			element.setAttribute(name, value);
		}
	}

	protected void addHandledException(Class exception) {
		handledExceptions.add(exception);
	}

	/**
	 * A utility class for selecting certain attributes. If the attributes are
	 * enabled, the default is set to block others; if specific attributes are
	 * disabled the default is set to allow others.
	 *
	 * @param delegate
	 * @param attributes
	 * @param enable
	 */
	public static void enableAttributes(IUuahDefinitionParser delegate,
			String[] attributes, boolean enable) {
		// if enabling specific attributes, block globally
		delegate.setIgnoredDefault(enable);

		Iterator names = Arrays.asList(attributes).iterator();
		while (names.hasNext()) {
			String name = (String) names.next();
			if (enable) {
				delegate.removeIgnored(name);
			} else {
				delegate.addIgnored(name);
			}
		}
	}

	public static void enableAttributes(IUuahDefinitionParser delegate,
			String[][] attributes) {
		for (int i = 0; i < attributes.length; ++i) {
			enableAttributes(delegate, attributes[i], true);
		}
	}

	public static void enableAttributes(IUuahDefinitionParser delegate,
			String[] attributes) {
		enableAttributes(delegate, attributes, true);
	}

	public static void enableAttribute(IUuahDefinitionParser delegate,
			String attribute) {
		enableAttributes(delegate, new String[] { attribute }, true);
	}

	public static void disableAttributes(IUuahDefinitionParser delegate,
			String[][] attributes) {
		for (int i = 0; i < attributes.length; ++i) {
			enableAttributes(delegate, attributes[i], false);
		}
	}

	public static void disableAttributes(IUuahDefinitionParser delegate,
			String[] attributes) {
		enableAttributes(delegate, attributes, false);
	}

	public static void disableAttribute(IUuahDefinitionParser delegate,
			String attribute) {
		enableAttributes(delegate, new String[] { attribute }, false);
	}

}
